﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Serialization;
using System.Text.RegularExpressions;
using Xww.IO.Serialize;

namespace Xww.LCV {
    [Serializable]
    public class LCS : IEqualityComparer<LCS>, IFormattable, ISerializable {

        public int[] Locations { get; internal set; }
        public char[] AAs { get; internal set; }

        #region Properties when extracted

        private int support;
        public int Support {
            get { return support; }
            set { support = value; }
        }
        public int Length {
            get { return Locations.Length; }
        }
        public int? Support2 { get; set; }
        public double? SupportDifference { get; set; }
        public double? SupportQuotient { get; set; }
        public double? PValue { get; set; } 

        string internalString;
        public string InternalString {
            get {
                if (internalString == null) {
                    StringBuilder sb = new StringBuilder();
                    sb.Append(AAs[0]);
                    for (int i = 1; i < Locations.Length; i++) {
                        int offset = Locations[i] - Locations[i - 1] - 1;
                        if (offset > 0)
                            sb.Append(new string(SEPARATOR, offset));
                        sb.Append(AAs[i]);
                    }
                    internalString = sb.ToString();
                }
                return internalString;
            }
        }

        #endregion


        #region Sub

        internal int LastLocation {
            get { return Locations[Locations.Length - 1]; }
        }
        internal int FirstLocation {
            get { return Locations[0]; }
        }

        public string SubFirstString {
            get {
                if (subFirstString == null) {
                    subFirstString = InternalString.Substring(0, Locations[Length-2]);
                }
                return subFirstString;
            }
        }
        string subFirstString;
        public string SubLastString {
            get {
                if (subLastString == null) {
                    subLastString = InternalString.Substring(Locations[1] - 1);
                }
                return subLastString;
            }
        }
        string subLastString;


        #endregion

        #region Regex
        Regex regex;
        public Regex REGEX {
            get {
                if (regex == null) {
                    regex = new Regex(GetRegexString());
                }
                return regex;
            }
        }
        public void DeleteRegex() {
            regex = null;
        }
        string regexString;
        public string RegexString {
            get {
                if (regexString == null)
                    regexString = GetRegexString();
                return regexString;
            }
        }
        public void DeleteRegexString() {
            regexString = null;
        }
        public string GetRegexString() {
            StringBuilder sb = new StringBuilder();
            sb.Append(AAs[0]);
            for (int i = 1; i < AAs.Length; i++) {
                int offset = Locations[i] - Locations[i - 1] - 1;
                if (offset > 0)
                    sb.AppendFormat("\\S{{{0}}}", offset);
                sb.Append(AAs[i]);
            }
            return sb.ToString();
        } 
        #endregion

        internal LCS() { }
        //for create LCS seed (length=1)
        public LCS(int support, char ch, int location) {
            // TODO: Complete member initialization
            this.Support = support;
            this.AAs = new char[] { ch };
            this.Locations = new int[] { location };
        }
        public LCS(char[] units, int[] positions) {
            Locations = new int[units.Length];
            AAs = units.Clone() as char[];
            positions.CopyTo(Locations, 0);
        }

        public const char SEPARATOR = '.';
        public readonly static LCS InstanceForHash = new LCS();
        //private int p;
        //private LCSUnit lCSUnit;

        #region IEqualityComparer<LCS> Members

        public bool Equals(LCS x, LCS y) {
            return x.InternalString == y.InternalString && x.FirstLocation == y.FirstLocation;
        }

        public int GetHashCode(LCS obj) {
            //return obj.GetHashCode();
            return obj.InternalString.GetHashCode();
        }

        #endregion

        #region To string & From string

        public override string ToString() {
            StringBuilder rst = new StringBuilder();
            rst.AppendFormat("{0}{1}", AAs[0], Locations[0]);
            for (int i = 1; i < Length; i++) {
                rst.AppendFormat(" {0}{1}", AAs[i], Locations[i]);
            }
            return rst.ToString();
        }

        public LCS LCVString { get { return this; } }

        public static explicit operator LCS(string str) {
            string[] strArray = str.Split(default(char[]), StringSplitOptions.RemoveEmptyEntries);
            int support = 0;
            if (strArray.Length < 1) {
                throw new InvalidCastException(string.Format("构建 LCS 的字符串[{0}]不符合规范！\n合法的 LCS 字符串应包括若干个 LCSUnit 和结尾的一个支持数(可选)，例：A1 B2 C3 5", str));
            }
            List<LCSUnit> list = new List<LCSUnit>();
            int index = 0;     // 指示最后一位是否包含支持数信息
            try {
                if (int.TryParse(strArray[strArray.Length - 1], out support)) {
                    index = 1;
                }
                for (int i = 0; i < strArray.Length - index; i++) {
                    list.Add((LCSUnit)strArray[i]);
                }
            }
            catch {
                throw new InvalidCastException(string.Format("构建LCS的字符串[{0}]不符合规范！\n合法的LCS字符串应包括若干个LCSUnit和结尾的一个支持数(可选)，例：A1 B2 C3 5", str));
            }
            LCS LCS = new LCS(list.Select(o => o.AA).ToArray(), list.Select(o => o.Location).ToArray());
            LCS.Support = support;
            return LCS;
        }

        #endregion

        #region encoding related
        /// <summary>Default relative mode match</summary>
        public bool IfContainedInSequence(string sequence) {
            return this.REGEX.IsMatch(sequence);
        }

        public bool IsParentOf(LCS sub) {
            return this.InternalString.Contains(sub.InternalString);
        }
        #endregion


        public static LCS ImergeFirstLast(LCS the, LCS other) {
            char[] rstAAs = new char[the.Length + 1];
            int[] rstLocations = new int[other.Length + 1];
            the.AAs.CopyTo(rstAAs, 0);
            the.Locations.CopyTo(rstLocations, 0);
            rstAAs[rstAAs.Length - 1] = other.AAs[other.Length - 1];
            rstLocations[rstLocations.Length - 1] = other.Locations[other.Length - 1] + the.Locations[1] - other.FirstLocation;
            return new LCS(rstAAs, rstLocations);
        }

        public bool IfContainedInSequence(string line, bool positionRelated) {
            if (positionRelated) {
                int start = FirstLocation-1;
                if (line.Length - start >= InternalString.Length) {
                    for (int i = 0; i < Locations.Length; i++) {
                        if (line[Locations[i] - 1] != AAs[i])
                            return false;
                    }
                    return true;
                }
                else
                    return false;
            }
            else
                return Regex.IsMatch(line, RegexString);
        }

        public int CountInSequnece(string line, bool isAbsolutePosition) {
            if (isAbsolutePosition) {
                return IfContainedInSequence(line, true) ? 1 : 0;
            }
            else {
                return REGEX.Matches(line).Count;
            }
        }

        public void ResetSupportNumber() {
            Support = 0;
        }

        public void ResetSuppor2tNumber() {
            Support2 = 0;
        }


        #region IFormattable Members

        public string ToString(string format, IFormatProvider formatProvider) {
            if (string.IsNullOrEmpty(format))
                return ToString();
            else
                return ToString(format);
        }
        public string ToString(string separator) {
            StringBuilder rst = new StringBuilder();
            rst.Append(AAs[0]);
            for (int i = 1; i < Length; i++) {
                int offset = Locations[i] - Locations[i - 1] - 1;
                for (int j = 0; j < offset; j++)
                    rst.Append(separator);
                rst.Append(AAs[i]);
            }
            //if (this.Length > 1) rst.Append(this[this.Length - 1].AA);
            return rst.ToString();
        }

        #endregion

        #region ISerializable 成员

        public void GetObjectData(SerializationInfo info, StreamingContext context) {
            SerializationWriter writer = new Xww.IO.Serialize.SerializationWriter();
            writer.Write(Support);
            writer.WriteNullable(Support2);
            writer.WriteNullable(SupportDifference);
            writer.WriteNullable(SupportQuotient);
            writer.WriteNullable(PValue);
            writer.Write(AAs);
            writer.Write(Locations);
            info.AddValue("data", writer.ToArray());
        }
        private LCS(SerializationInfo info, StreamingContext context) {
            Xww.IO.Serialize.SerializationReader reader = new Xww.IO.Serialize.SerializationReader(
                (byte[])info.GetValue("data", typeof(byte[])));
            Support = reader.ReadInt32();
            Support2 = reader.ReadNullableInt32();
            SupportDifference = reader.ReadNullableDouble();
            SupportQuotient = reader.ReadNullableDouble();
            PValue = reader.ReadNullableDouble();
            AAs = reader.ReadCharArray();
            Locations = reader.ReadInt32Array();
        }


        #endregion

        // methods below are used to compate with former version in absolute mode.
        internal static bool CanImerge(LCS a, LCS b) {
            if (a.Length != b.Length)
                return false;
            if (a.Length == 1)
                return a.FirstLocation != b.FirstLocation;
            if (a.LastLocation == b.LastLocation)
                return false;
            for (int i = 0; i < a.Length - 1; i++) {
                if (a.Locations[i] != b.Locations[i] || a.AAs[i] != b.AAs[i])
                    return false;
            }
            return true;
        }

        internal static LCS Imerge(LCS a, LCS b) {
            LCS rst = new LCS();
            char[] units = new char[a.Length +1];
            int[] locations = new int[units.Length];
            rst.AAs = units; rst.Locations = locations;
            for (int i = 0; i < a.AAs.Length -1; i++) {
                rst.AAs[i] = a.AAs[i];
                rst.Locations[i] = a.Locations[i];
            }
            LCS former = a;
            LCS latter = b;
            if (a.LastLocation > b.LastLocation) {
                former = b;
                latter = a;
            }
            rst.Locations[former.Length -1] = former.LastLocation;
            rst.AAs[former.Length -1] = former.AAs[former.Length-1];
            rst.Locations[latter.Length] = latter.LastLocation;
            rst.AAs[latter.Length] = latter.AAs[latter.Length - 1];
            return rst;
        }

        internal bool IsFitForQSet(IEnumerable<string> QuintessentialSet, int threshold) {
            this.Support = QuintessentialSet.Count(o => this.IfContainedInSequence(o, true));
            return  this.Support >= threshold;
        }
    }
}
